package edu.xhu.homepage.service.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import edu.xhu.homepage.mapper.HotVideoMapper;
import edu.xhu.homepage.service.BiliVideoService;
import edu.xhu.homepage.service.HotVideoService;
import edu.xhu.model.common.constants.HotVideoConstants;
import edu.xhu.model.common.dtos.ResponseResult;
import edu.xhu.model.common.enums.AppHttpCodeEnum;
import edu.xhu.model.video.pojos.BiliVideo;
import edu.xhu.model.video.pojos.BiliVideoData;
import edu.xhu.model.video.vos.HotVideoVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;


import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class HotVideoServiceImpl implements HotVideoService {

    private final HotVideoMapper hotVideoMapper;

    private final BiliVideoService biliVideoService;

    private final StringRedisTemplate redisTemplate;

    //双检加锁之锁对象
    private final Object hotVideoQueryLock = new Object();

    /**
     * 查询热点视频
     *
     * @return List<HotVideoVo>
     */
    @Override
    public ResponseResult<?> getHotVideos() {
        //存储热点视频查询结果
        List<HotVideoVo> resultHotVideoVoList = null;

        //先查询redis缓存
        List<String> hotVideoVoJsonList = null;

        //获取redis缓存中的热点视频json数据, 随机查找11个热点视频
        //先查询A缓存
        hotVideoVoJsonList = this.redisTemplate.opsForSet().randomMembers(HotVideoConstants.HOT_VIDEOS_KEY_A, 11);
        if (hotVideoVoJsonList == null || hotVideoVoJsonList.size() < 11) {
            //缓存A未命中，再去查缓存B
            hotVideoVoJsonList = this.redisTemplate.opsForSet().randomMembers(HotVideoConstants.HOT_VIDEOS_KEY_B, 11);
            if (hotVideoVoJsonList == null || hotVideoVoJsonList.size() < 11) {
                //缓存B也没命中，走DB，双检加锁之
                synchronized (hotVideoQueryLock) {
                    //再来一波查双层缓存
                    hotVideoVoJsonList = this.redisTemplate.opsForSet().randomMembers(HotVideoConstants.HOT_VIDEOS_KEY_A, 11);

                    if (hotVideoVoJsonList == null || hotVideoVoJsonList.size() < 11)
                        hotVideoVoJsonList = this.redisTemplate.opsForSet().randomMembers(HotVideoConstants.HOT_VIDEOS_KEY_B, 11);

                    if (hotVideoVoJsonList == null || hotVideoVoJsonList.size() < 11) {
                        //仍然未命中缓存
                        //走DB查询最热视频json字符串
                        System.out.println("热点视频打到db了");

                        //拿到热点视频的json数组，走DB
                        String[] hotVideoVoJsonArray = this.calculateAndGetHotVideosJsonArray();

                        //手动刷新redis中的缓存
                        this.redisTemplate.delete(HotVideoConstants.HOT_VIDEOS_KEY_B);
                        this.redisTemplate.opsForSet().add(HotVideoConstants.HOT_VIDEOS_KEY_B, hotVideoVoJsonArray);
                        this.redisTemplate.delete(HotVideoConstants.HOT_VIDEOS_KEY_A);
                        this.redisTemplate.opsForSet().add(HotVideoConstants.HOT_VIDEOS_KEY_A, hotVideoVoJsonArray);


                        //随机获取11个视频的json字符串
                        if (hotVideoVoJsonArray.length >= 11) {
                            //将数组转换为列表后进行随机排列，然后选择前 n 个元素。
                            List<String> list = Arrays.asList(hotVideoVoJsonArray);
                            Collections.shuffle(list);

                            // 返回前 11 个元素
                            hotVideoVoJsonList = list.subList(0, 11);
                        }


                    }

                }
            }
        }

        if (hotVideoVoJsonList == null || hotVideoVoJsonList.size() < 11) {
            //异常，无法查询到最热视频的json数据，redis，mysql都失败了
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "无法查询到最热视频的json数据，redis，mysql都失败了");
        }


        resultHotVideoVoList = hotVideoVoJsonList.stream().
                map(json -> {
                    return JSONUtil.toBean(json, new TypeReference<HotVideoVo>() {
                    }, true);
                }).collect(Collectors.toList());

        return ResponseResult.okResult(resultHotVideoVoList);
    }


    /**
     * 刷新热点视频至redis缓存
     */
    @Scheduled(fixedRate = 300000) // 每5分钟刷新一次缓存 (300,000毫秒 = 5分钟)
    private void refreshHotVideoCache() {
        //拿到热点视频的json数组，走DB
        String[] hotVideoVoJsonArray = this.calculateAndGetHotVideosJsonArray();

        //双层缓存
        //先更新B缓存
        this.redisTemplate.delete(HotVideoConstants.HOT_VIDEOS_KEY_B);

        this.redisTemplate.opsForSet().add(HotVideoConstants.HOT_VIDEOS_KEY_B, hotVideoVoJsonArray);

        //然后更新A缓存
        this.redisTemplate.delete(HotVideoConstants.HOT_VIDEOS_KEY_A);

        this.redisTemplate.opsForSet().add(HotVideoConstants.HOT_VIDEOS_KEY_A, hotVideoVoJsonArray);

        System.out.println("刷新了热点视频redis");
    }

    /**
     * 计算热点视频数据并返回String[],数组中每个元素都是单个视频的json
     *
     * @return 热点视频的String[]
     */
    private String[] calculateAndGetHotVideosJsonArray() {
        //存储热点视频所有信息
        //<videoid,HotVideoVo>
        // 存储视频ID (videoid) 与 HotVideoVo 对象的对应关系
        // 键 (Long): 视频ID
        // 值 (HotVideoVo): 包含视频信息的对象
        Map<Long, HotVideoVo> hotVideoVoMap = new ConcurrentHashMap<>();

        //查询最近INTERVAL_DAYS天内视频数据更新过，分值最高的LIMIT条视频
        List<BiliVideoData> biliVideoDatas = hotVideoMapper.getTopVideos(
                HotVideoConstants.PLAY_WEIGHT,
                HotVideoConstants.DANMU_WEIGHT,
                HotVideoConstants.LIKES_WEIGHT,
                HotVideoConstants.COIN_WEIGHT,
                HotVideoConstants.COLLECT_WEIGHT,
                HotVideoConstants.SHARES_WEIGHT,
                HotVideoConstants.COMMENTS_WEIGHT,
                HotVideoConstants.INTERVAL_DAYS, HotVideoConstants.LIMIT);

        //拿到热点视频的所有 id
        List<Long> videoIds = biliVideoDatas.stream()
                .map(BiliVideoData::getVideoId)
                .collect(Collectors.toList());

        //判空逻辑
        if (videoIds == null || videoIds.size() == 0)
            return new String[0];

        //拿到热点视频的详细信息，即BiliVideo
        List<BiliVideo> biliVideos = biliVideoService.lambdaQuery().in(BiliVideo::getVideoId, videoIds).list();


        //打包需要缓存的数据至hotVideoVoMap
        //封装BiliVideoData数据
        //使用 foreach 循环遍历并操作每个对象
        biliVideoDatas.forEach(biliVideoData -> {
            HotVideoVo hotVideoVo = new HotVideoVo();
            hotVideoVo.setBiliVideoData(biliVideoData);
            hotVideoVoMap.put(biliVideoData.getVideoId(), hotVideoVo);
        });
        //封装BiliVideo数据
        biliVideos.forEach(biliVideo -> {
            hotVideoVoMap.get(biliVideo.getVideoId()).setBiliVideo(biliVideo);
        });

        //判空逻辑，保证某个videoId的biliVideoData与biliVideo同时不为空
        hotVideoVoMap.keySet().forEach(videoId -> {
            if (hotVideoVoMap.get(videoId).getBiliVideo() == null)
                hotVideoVoMap.remove(videoId);
        });

        // 将 map 的value值转换为 List, 拿到热点视频数据list
        List<HotVideoVo> hotVideoVoList = new ArrayList<>(hotVideoVoMap.values());

        //将 List<HotVideoVo> 中的每个元素转换为 JSON 字符串
        List<String> hotVideoVoJsonList = hotVideoVoList.stream()
                .map(JSONUtil::toJsonStr)
                .collect(Collectors.toList());

        // 将 List 转换为 String 数组
        String[] hotVideoVoJsonArray = hotVideoVoJsonList.toArray(new String[hotVideoVoJsonList.size()]);

        return hotVideoVoJsonArray;
    }

}
